/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package TMS;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Zachary
 */
public class TMS {
    enum Machine_State {Stopped, Initializing, Running, Paused}
    workout_settings current_settings;
    workout_session current_session;
    User_Profile current_profile;
    Machine_State machine_state;
    /* TMS Constructor */
    public TMS(){
        current_settings = null;
        current_session = null;
        current_profile = new User_Profile();
        machine_state = Machine_State.Stopped;
        Output_Device.set_output("Treadmill Monitoring System Started...\nPlease login or start a workout.");
    }
    /* Start/Pause/Stop Controls */
    public void Start_Workout(){
        if (Safety_Clip.is_engaged()){
            /* If the machine is Stopped, initialize session */
            if (machine_state == Machine_State.Stopped){
                machine_state = Machine_State.Initializing;
                new SessionRunner().start();
            }
            /* If the machine is Paused, set it to Running */
            else{
                if (machine_state == Machine_State.Paused){
                    machine_state = Machine_State.Running;
                }
            }
        }
    }
    public void Pause_Workout(){
        if (machine_state == Machine_State.Running){
            machine_state = Machine_State.Paused;
        }
    }
    public void Stop_Workout() throws FileNotFoundException, IOException{
        if (machine_state != Machine_State.Stopped){
            machine_state = Machine_State.Stopped;
            if (current_profile.get_logged_in())Storage_Device.save_profile(current_profile);
        }
    }
    /* Speed Controls */
    public void Speed_Increase(){
        if (machine_state == Machine_State.Running){
            if (current_settings.current_speed +.1 <= Treadmill.max_speed){
                current_settings.current_speed += .1;
                Treadmill.set_speed(current_settings.current_speed);
            }
        }
    }
    public void Speed_Decrease(){
        if (machine_state == Machine_State.Running){
            if (current_settings.current_speed -.1 > 0){
                current_settings.current_speed -= .1;
                Treadmill.set_speed(current_settings.current_speed);
            }
        }
    }
    /* Incline Controls*/
    public void Incline_Increase(){
        if (machine_state == Machine_State.Running){
            if (current_settings.current_incline + 1 <= Treadmill.max_incline){
                current_settings.current_incline += 1;
                Treadmill.set_incline(current_settings.current_incline);
            }
        }

    }
    public void Incline_Decrease(){
        if (machine_state == Machine_State.Running){
            if (current_settings.current_incline - 1 >= 0){
                current_settings.current_incline -= 1;
                Treadmill.set_incline(current_settings.current_incline);
            }
        }
    }
    /* Workout Profile Selection */
    public void set_workout_profile(workout_settings.Workout_Mode mode, workout_settings.Workout_Dificulty dificulty){
        if (machine_state == Machine_State.Initializing || machine_state == Machine_State.Running ){
            current_settings.set_Mode(mode, dificulty);
        }
    }
    /* User Account Controls */
    public void login() throws FileNotFoundException, IOException{
        if (machine_state == Machine_State.Stopped){
            if (!current_profile.get_logged_in()){
                new Thread(){
                    @Override
                    public void run(){
                        String user_name; int user_pin = 0;
                        Input_Device.clear();
                        while (!Input_Device.is_ready()){
                            Output_Device.set_output("Input User Name: " + Input_Device.input_value());
                        }
                        user_name = Input_Device.getInput();
                        while (!Input_Device.is_ready()){
                            Output_Device.set_output("Input User Pin: " + Input_Device.input_value());
                        }
                        if (Input_Device.isNumeric()) user_pin = Integer.parseInt(Input_Device.getInput());

                        try {
                            current_profile = Storage_Device.get_profile(user_name, user_pin);
                        } catch (FileNotFoundException ex) {
                            Logger.getLogger(TMS.class.getName()).log(Level.SEVERE, null, ex);
                        } catch (IOException ex) {
                            Logger.getLogger(TMS.class.getName()).log(Level.SEVERE, null, ex);
                        }
                        Output_Device.set_output("Logged in as " + current_profile.get_user_name());
                    }
                }.start();
            } else {
                Storage_Device.save_profile(current_profile);
                current_profile = new User_Profile();
                Output_Device.set_output("Logged in as " + current_profile.get_user_name());
            }
        }
    }
    public void new_user() throws FileNotFoundException, IOException{
        if (machine_state == Machine_State.Stopped){
            new Thread(){
                @Override
                public void run(){
                    String user_name = "";
                    int user_pin = 0;
                    Input_Device.clear();
                    while (!Input_Device.is_ready()){
                        Output_Device.set_output("New User Name: " + Input_Device.input_value());
                    }
                    user_name = Input_Device.getInput();
                    Input_Device.clear();
                    while (!Input_Device.isNumeric()){
                        while (!Input_Device.is_ready()){
                            Output_Device.set_output("New User Pin: " + Input_Device.input_value());
                        }
                    }
                    if (Input_Device.isNumeric()) user_pin = Integer.parseInt(Input_Device.getInput());

                    current_profile = new User_Profile();
                    current_profile.set_user_name(user_name);
                    current_profile.set_user_pin(user_pin);
                    try {
                        /* Save New Profile */
                        Storage_Device.new_profile(current_profile);
                        current_profile = Storage_Device.get_profile(user_name, user_pin);                    
                    } catch (FileNotFoundException ex) {
                        Logger.getLogger(TMS.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (IOException ex) {
                        Logger.getLogger(TMS.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    if (current_profile.get_logged_in()) Output_Device.set_output("Successfully registered user: " + user_name);
                    else{
                        Output_Device.set_output("User registry unsuccessful");
                        current_profile = new User_Profile();
                    }
                    }
            }.start();
        }
    }
    public String user_history(){
        return current_profile.toString();
    }
    /* Treadmill Operation */
    class SessionRunner extends Thread{
        
        public SessionRunner(){
            
        }        
        private void Initialize(){
            double weight = 0;
            double target_time = 0;
            double target_calories = 0;
            current_session = current_profile.new_session();
            current_settings = new workout_settings();

            /* Get User Input */
            //Get Weight
            Input_Device.clear();
            while (!Input_Device.isNumeric()){
                while (!Input_Device.is_ready()){
                    Output_Device.set_output("Enter Current Weight (Pounds): " + Input_Device.input_value());
                }
            }
            weight = Double.valueOf(Input_Device.getInput());
            //Get Target Time
            Input_Device.clear();
            while (!Input_Device.isNumeric()){
                while (!Input_Device.is_ready()){
                    Output_Device.set_output("Enter Target Time (Minutes): " + Input_Device.input_value());
                }
            }
            target_time = Double.valueOf(Input_Device.getInput()) * 60;
            //Get Target Calories
            Input_Device.clear();
            while (!Input_Device.isNumeric()){
                while (!Input_Device.is_ready()){
                    Output_Device.set_output("Enter Target Calories: " + Input_Device.input_value());
                }
            }
            target_calories = Double.valueOf(Input_Device.getInput());
            
            /* Apply User Input */
            current_session.current_weight = weight;
            current_settings.set_start_time(Clock.get_time());
            current_settings.set_target_time(target_time);
            current_settings.set_target_calories(target_calories);
            current_settings.set_current_incline(0);
            current_settings.set_current_speed(.1);

            /* Start the machine */
            machine_state = Machine_State.Running;
            Treadmill.kill();
            Treadmill.set_speed(.1);
        }
        private String display(){
            String output_string = "";
            java.text.NumberFormat fmt = java.text.NumberFormat.getInstance();
            fmt.setMaximumFractionDigits(5);
            fmt.setMinimumFractionDigits(5);               
            output_string = ((Safety_Clip.is_engaged())? "Safety Clip Engaged.": "Safety Clip Disengaged.") + " " + machine_state;
            output_string += "\nUser:\t" + current_profile.get_user_name();
            output_string += "\nTraining Mode:\t" + current_settings.current_mode();
            output_string +=
                    "\nTime:\t" + fmt.format(current_session.session_time / 60) + " minutes / " +
                    fmt.format(current_settings.get_target_time() / 60) + " minutes";
            output_string += "\nDistance:\t(Total): " + fmt.format(current_session.distance_run) + " miles";
            output_string += "\n\t\t(Climbed): " + fmt.format(current_session.distance_climbed) + " miles";
            output_string += "\nHeart Rate:\t" + fmt.format(current_session.heartrate_current) + " BPM";
            output_string += "\nCalories Burned: " + fmt.format(current_session.calories_burned) + " / " + fmt.format(current_settings.get_target_calories());
            output_string += "\nSpeed:\t" + fmt.format(Treadmill.get_current_speed()) +" MPH / " + fmt.format(current_settings.current_speed)+" MPH";
            output_string += "\nIncline:\t" + fmt.format(Treadmill.get_current_incline()) +" Degrees / " + fmt.format(current_settings.current_incline)+" Degrees";
            return output_string;
        }
        public void calculate_calories(double dCurrentSpeed, double dGrade, double dWeight)    {
            //Equations dervied from this webpage:
            //   http://www.myfitnesspal.com/topics/show/134478-accurate-formula-to-determine-calories-burned-jogging
            if (dCurrentSpeed == 0)
                return;
            double dCalories = 0;
            double dMetersPerMinute = dCurrentSpeed * 26.8;
            double dPercentGrade = dGrade / 100;
            double dKilograms = dWeight / 2.2;
            if (dCurrentSpeed <= 3.7)
                dCalories = (0.1 * dMetersPerMinute) + (1.8 * dMetersPerMinute * dPercentGrade) + 3.5;
            else
                dCalories = (0.2 * dMetersPerMinute) + (0.9 * dMetersPerMinute * dPercentGrade) + 3.5;
            dCalories = ((dCalories / 3.5) * dKilograms) / 3600;
            current_session.calories_burned += dCalories;
        }
        @Override
        public void run(){
            Initialize();
            double current_time = Clock.get_time();
            double previous_time = current_time;
            double total_HR =0;
            double total_HR_polls = 0;
            
            while (machine_state != Machine_State.Stopped){
                /* If the safety clip is engaged & machine is in a running state */                
                Output_Device.set_output(display());
                if (Safety_Clip.is_engaged() && machine_state == Machine_State.Running){
                    /* Calculate Time Delta*/
                    previous_time = current_time; current_time = Clock.get_time();
                    double delta_time = current_time - previous_time;
                    /* Calculate Distance Delta */
                    double delta_distance = delta_time * Treadmill.get_current_speed() / 3600;
                    /* Increment distance run and climbed*/
                    current_session.distance_run += delta_distance;
                    current_session.distance_climbed += delta_distance * Math.sin(Treadmill.get_current_incline() * Math.PI / 180);
                    /* Increment time */
                    current_session.session_time += delta_time;
                    /* Get heart rate */
                    current_session.heartrate_current = Heart_Monitor.get_heart_rate();
                    /* Update peak Heartrate */
                    if (current_session.heartrate_current > current_session.heartrate_peak)current_session.heartrate_peak = current_session.heartrate_current;
                    /* Update average heartrate every 15 seconds */
                    if (current_session.heartrate_current != 0 && (current_session.session_time % 15 < 1) ){
                        total_HR += current_session.heartrate_current;
                        total_HR_polls ++;
                        current_session.heartrate_average = total_HR / total_HR_polls;
                    }
                    /* update calorie burned */
                    calculate_calories(current_settings.get_current_speed(), 100 * Math.tan(current_settings.get_current_incline() * Math.PI/180), current_session.current_weight);
                    /* Check if current workout profile changes the treadmill settings */
                    boolean adjust = current_settings.adjustSettings(current_session.session_time,0);
                    if (adjust){
                        Treadmill.set_speed(current_settings.get_current_speed());
                        Treadmill.set_incline(current_settings.get_current_incline());
                    }
                    /* Check if the current goals have been met. */
                    if (current_settings.workout_done(current_session.session_time, current_session.calories_burned)) machine_state = Machine_State.Stopped;
                    
                    Clock.wait(1.0);
                }
                /* If the safety-clip is disengaged, pause the machine */
                else{
                    if ((!Safety_Clip.is_engaged() && machine_state == Machine_State.Running) || (machine_state == Machine_State.Paused)){
                        machine_state = Machine_State.Paused;
                        Treadmill.kill();
                        while (!Safety_Clip.is_engaged() || machine_state == Machine_State.Paused){
                            /*Wait until the safety clip is engaged and the machine is no longer paused*/
                            previous_time = current_time;
                            current_time = Clock.get_time();                            
                            Output_Device.set_output(display());
                        }
                        Treadmill.set_speed(current_settings.current_speed);
                        Treadmill.set_incline(current_settings.current_incline);
                    }                    
                }                
            }
            Treadmill.kill();
            Output_Device.set_output("Session Ended");
            if (current_profile.get_logged_in()) try {
                Storage_Device.save_profile(current_profile);
            } catch (FileNotFoundException ex) {
                Logger.getLogger(TMS.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(TMS.class.getName()).log(Level.SEVERE, null, ex);
            }
        }                
    }
}
